package entidades;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import excepciones.VuelosException;

public class FlightController implements IFlightController {

	private List<Vuelo> vuelos;
	private List<Aeropuerto> aeropuertos;
	private List<Reserva> reservas;

	public FlightController() {
		this.vuelos = new ArrayList<Vuelo>();
		this.aeropuertos = new ArrayList<Aeropuerto>();
		this.reservas = new ArrayList<Reserva>();
	}

	@Override
	public List<Vuelo> getFlights() {
		return vuelos;
	}

	public void setVuelos(List<Vuelo> vuelos) {
		this.vuelos = vuelos;
	}

	@Override
	public List<Aeropuerto> getAirports() {
		return aeropuertos;
	}

	public void setAeropuertos(List<Aeropuerto> aeropuertos) {
		this.aeropuertos = aeropuertos;
	}

	public List<Reserva> getReservations() {
		return reservas;
	}

	public void setReservas(List<Reserva> reservas) {
		this.reservas = reservas;
	}

	@Override
	public void addAirport(Aeropuerto airport) throws VuelosException {
		for (Aeropuerto aero : this.aeropuertos) {
			if (aero.getName().equals(airport.getName())) {
				throw (new VuelosException("Ya existe un aeropuerto con el Nombre ingresado"));
			}
		}

		this.aeropuertos.add(airport);
	}

	@Override
	public void addFlight(Vuelo flight) throws VuelosException {
		for (Vuelo v : this.vuelos) {
			if (v.equals(flight)) {
				throw (new VuelosException("Ya existe el vuelo ingresado"));
			}
		}

		this.vuelos.add(flight);
	}

	/**
	 * Devuelve una lista de cons dos caminos. La primer posicion corresponde al
	 * camino de menor costo y la seunda posicion corresponde al camino con
	 * menor tiempo
	 */
	@Override
	public HashMap<String, List<Vuelo>> getRoutes(Aeropuerto origen, Aeropuerto destino, int cantAsientos, int esperaMax, Date fecha) throws VuelosException {
		double menorPrecio = 0;
		long menorTiempo = 0;

		HashMap<String, List<Vuelo>> result = new HashMap<String, List<Vuelo>>();
		List<List<Vuelo>> caminos = new ArrayList<List<Vuelo>>();

		List<Vuelo> vuelosDesde = this.obtenerVuelosDesde(origen, cantAsientos, fecha);

		for (Vuelo vuelo : vuelosDesde) {
			List<Aeropuerto> recorrido = new ArrayList<Aeropuerto>();
			recorrido.add(origen);

			if (vuelo.getDestino().equals(destino)) {
				List<Vuelo> camino = new ArrayList<Vuelo>();
				camino.add(vuelo);
				caminos.add(camino);
			} else {
				List<List<Vuelo>> subCaminos = this.obtenerCaminos2(vuelo.getDestino(), destino, cantAsientos, esperaMax, vuelo.getLlegada(), recorrido);
				for (List<Vuelo> subCamino : subCaminos) {
					if (subCamino.get(subCamino.size() - 1).getDestino().equals(destino)) {
						subCamino.add(0, vuelo);
						caminos.add(subCamino);
					}
				}
			}
		}

		for (int i = 0; i < caminos.size(); i++) {
			double precio = 0;
			long tiempo = 0;

			List<Vuelo> camino = caminos.get(i);
			for (int j = 0; j < camino.size(); j++) {
				precio += camino.get(j).getPrecio();

				tiempo += Math.abs(camino.get(j).getLlegada().getTime() - camino.get(j).getSalida().getTime());
				if (j > 0) {
					tiempo += Math.abs(camino.get(j).getSalida().getTime() - camino.get(j - 1).getLlegada().getTime());
				}
			}

			if (i == 0) {
				menorPrecio = precio;
				result.put("precio", camino);

				menorTiempo = tiempo;
				result.put("tiempo", camino);
			} else {
				if (precio < menorPrecio) {
					menorPrecio = precio;
					result.put("precio", camino);
				}

				if (tiempo < menorTiempo) {
					menorTiempo = tiempo;
					result.put("tiempo", camino);
				}
			}
		}

		return result;
	}

	public List<List<Vuelo>> obtenerCaminos2(Aeropuerto origen, Aeropuerto destino, int cantAsientos, int esperaMax, Date fecha, List<Aeropuerto> recorrido) throws VuelosException {
		List<List<Vuelo>> caminos = new ArrayList<List<Vuelo>>();

		recorrido.add(origen);

		List<Vuelo> vuelosDesde = this.obtenerVuelosDesde(origen, cantAsientos, esperaMax, fecha, recorrido);
		for (Vuelo vuelo : vuelosDesde) {
			if (vuelo.getDestino().equals(destino)) {
				List<Vuelo> camino = new ArrayList<Vuelo>();
				camino.add(vuelo);
				caminos.add(camino);
			} else {
				List<List<Vuelo>> subCaminos = this.obtenerCaminos2(vuelo.getDestino(), destino, cantAsientos, esperaMax, vuelo.getLlegada(), recorrido);
				for (List<Vuelo> subCamino : subCaminos) {
					if (subCamino.get(subCamino.size() - 1).getDestino().equals(destino)) {
						subCamino.add(0, vuelo);
						caminos.add(subCamino);
					}
				}
			}
		}

		return caminos;
	}

	@Override
	public void reservar(Reserva reserva) throws VuelosException {
		for (Vuelo vuelo : reserva.getVuelos()) {
			vuelo.setCantDisponible(vuelo.getCantDisponible() - reserva.getCantidad());
		}
		this.reservas.add(reserva);
	}

	public List<Vuelo> obtenerVuelosDesde(Aeropuerto origen, int cantAsientos, int esperaMax, Date fecha, List<Aeropuerto> recorrido) throws VuelosException {
		List<Vuelo> vuelosDesde = new ArrayList<Vuelo>();
		for (Vuelo vuelo : this.vuelos) {
			if (vuelo.getOrigen().equals(origen) && vuelo.getEstado().equals(Estado.Anunciado)) {
				if (!recorrido.contains(vuelo.getDestino())) {
					long dif = Math.abs(fecha.getTime() - vuelo.getSalida().getTime());
					dif = dif / (60 * 60 * 1000); // obtengo diferencia en horas

					if (dif >= 0 && dif <= esperaMax && vuelo.getCantDisponible() >= cantAsientos) {
						vuelosDesde.add(vuelo);
					}
				}
			}
		}
		return vuelosDesde;
	}

	public List<Vuelo> obtenerVuelosDesde(Aeropuerto origen, int cantAsientos, Date fecha) throws VuelosException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		List<Vuelo> vuelosDesde = new ArrayList<Vuelo>();
		for (Vuelo vuelo : this.vuelos) {
			if (vuelo.getOrigen().equals(origen) && vuelo.getCantDisponible() >= cantAsientos && sdf.format(vuelo.getSalida()).equals(sdf.format(fecha)) && vuelo.getEstado().equals(Estado.Anunciado)) {
				vuelosDesde.add(vuelo);
			}
		}
		return vuelosDesde;
	}

	public String toString() {
		String obj = "";

		for (Vuelo vuelo : this.vuelos) {
			obj += vuelo.getNroVuelo() + ": " + vuelo.getOrigen().getName() + " -> " + vuelo.getDestino().getName() + "\n";
		}

		return obj;
	}

	@Override
	public void deleteAirport(Aeropuerto airport) throws VuelosException {
		for (Vuelo flight : this.getFlights()) {
			if (flight.getOrigen().equals(airport) || flight.getDestino().equals(airport)) {
				throw new VuelosException("El aeropuerto pertenece al vuelo n° " + flight.getNroVuelo());
			}
		}

		for (Iterator<Aeropuerto> i = this.getAirports().iterator(); i.hasNext();) {
			Aeropuerto element = i.next();
			if (element.equals(airport)) {
				i.remove();
				return;
			}
		}
	}

	@Override
	public String checkAirport(Aeropuerto airport) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteFlight(Vuelo flight) throws VuelosException {
		for (Reserva reserva : this.reservas) {
			if (reserva.getVuelos().contains(flight)) {
				throw new VuelosException("El vuelo tiene reservas, no se puede borrar");
			}
		}
		// if (flight.hasReservas()) {
		// throw new VuelosException("El vuelo tiene " +
		// flight.getReservas().size() + "reservas, no se puede borrar");
		// }
		for (Iterator<Vuelo> i = this.getFlights().iterator(); i.hasNext();) {
			Vuelo element = i.next();
			if (element.equals(flight)) {
				i.remove();
				return;
			}
		}
	}

	@Override
	public Estado checkFlight(Vuelo flight, Date actualTime) {
		return flight.getEstado();
	}

	@Override
	public void cancelReservation(Reserva reservation) {
		for (Vuelo flight : reservation.getVuelos()) {
			flight.setCantDisponible(flight.getCantDisponible() + reservation.getCantidad());
		}
		this.reservas.remove(reservation);
	}

	@Override
	public void setflightSeatus(Vuelo flight, Estado status) throws VuelosException {
		flight.setEstado(status);
	}

}
